/*
* is3729 led lattice driver
* AUTHOR: chiliang.cl@alibaba-inc.com
*
*/
#include <stdbool.h>
#include <stdlib.h>
#include <aos/hal/gpio.h>
#include <aos/hal/i2c.h>
#include <aos/debug.h>
#include <aos/kernel.h>

#include "is3729_drv.h"
#include "drv_display.h"

#define TAG "is3729_drv"

struct is3729_chip {
    int sdb_gpio;
    i2c_dev_t *i2c_dev;
    uint16_t i2c_dev_addr;
    int initialized;

    int nrow;           // number of row
    int ncol;           // number of column
    int size;           // num of leds = nrow * ncol
    int led_num;
    unsigned char reg_map[IS3729_MAX_LED_LEN];
    unsigned char led_map[IS3729_MAX_LED_LEN];

    unsigned char config_reg;
    unsigned char global_current_reg;
    unsigned char pwm_start_reg;
    unsigned char cs_scaling_start_reg;
    unsigned char pwm_freq_reg;
    unsigned char reset_reg;
    unsigned char pull_downup_reg;
    unsigned char spread_spectrum_reg;
    unsigned char open_short_start_reg;

    bool is_fb_bypass;
    bool is_gamma_enable;
    bool is_esd_guard_enable;
    bool is_lowest_sec;
    unsigned char brightness;
    unsigned char config;
    // unsigned char prev_buf[IS3729_BUF_LEN];
    // unsigned char lowest_lattice_pwm[IS3729_BUF_LEN];
};

enum {
    LED_INIT_NONE = 0,
    LED_INIT_NORESET, 
    LED_INIT_ALL
};

typedef struct {
    aos_dev_t  device;
    void   *screen_handle;
} mdisplay_dev_t;

#define EVT_TRANSFER_DONE   1
#define EVT_BUS_ERROR       (1<<1)

#define led(dev) ((mdisplay_dev_t *)dev)

static void _is3729_chip_init(struct is3729_chip *chip, int init_type);
static bool _is3729_chip_init_check(struct is3729_chip *chip);
static void _is3729_set_brightness(void *chip_led, unsigned char brightness);
static void _is3729_show(void *chip_led, unsigned char *led_buf);

static void gpio_write(int pin, int level)
{
    gpio_dev_t gpio = {pin, OUTPUT_PUSH_PULL, NULL};

    hal_gpio_init(&gpio);
    
    if (level) {
        hal_gpio_output_high(&gpio);
    } else {
        hal_gpio_output_low(&gpio);
    }

    hal_gpio_finalize(&gpio);
}

int i2c_write_reg(i2c_dev_t *dev, uint8_t reg, uint8_t val)
{
    uint8_t reg_val[2] = {reg, val};

    // LOGD(TAG, "3729", "i2c_write_reg %d %d", reg, val);
    return hal_i2c_master_send(dev, IS3729_I2C_ADDR, reg_val, 2, IS3729_I2C_TIMEOUT);
}

int i2c_read_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *val)
{
    uint8_t reg_val[1] = {reg};
    int ret;

    ret = hal_i2c_master_send(dev, IS3729_I2C_ADDR, reg_val, 1, IS3729_I2C_TIMEOUT);
    CHECK_RET_WITH_RET(ret == 0, -1);

    return hal_i2c_master_recv(dev, IS3729_I2C_ADDR, val, 1, IS3729_I2C_TIMEOUT);
}


static int i2c_write_regs(i2c_dev_t *dev, uint8_t reg_start, uint8_t *vals, int cnt)
{
    uint8_t reg_val[128] = {reg_start};

    CHECK_RET_WITH_RET(cnt < 128, -1);

    for (int i = 1; i < cnt + 1; i++)
    {
        reg_val[i] = vals[i - 1];
    }

    return hal_i2c_master_send(dev, IS3729_I2C_ADDR, reg_val, cnt + 1, IS3729_I2C_TIMEOUT);
}


static int _is3729_set_config(struct is3729_chip *chip, unsigned char config)
{
    return i2c_write_reg(chip->i2c_dev, chip->config_reg, config);
}

static int _is3729_set_global_current(struct is3729_chip *chip, unsigned char current_step)
{
    if (current_step > IS3729_GCC_MAX_VAL) {
        current_step = IS3729_GCC_MAX_VAL;
        LOGD(TAG, "%s: current_step:%d should lower than GCC_MAX:%d", __func__, current_step, IS3729_GCC_MAX_VAL);
    }

    return i2c_write_reg(chip->i2c_dev, chip->global_current_reg, current_step);
}

static int _is3729_set_cs_scaling(struct is3729_chip *chip, unsigned char cs_index, unsigned char scaling_val)
{
    unsigned char cs_scaling_reg;

    cs_scaling_reg = chip->cs_scaling_start_reg + cs_index;

    return i2c_write_reg(chip->i2c_dev, cs_scaling_reg, scaling_val);
}

static int _is3729_set_pwm_frequency(struct is3729_chip *chip, unsigned char pwm_freq_step)
{
    return i2c_write_reg(chip->i2c_dev, chip->pwm_freq_reg, pwm_freq_step);
}

static int _is3729_set_pull_downup(struct is3729_chip *chip, unsigned char pull_downup_config)
{
    return i2c_write_reg(chip->i2c_dev, chip->pull_downup_reg, pull_downup_config);
}

static int _is3729_set_spread_spectrum(struct is3729_chip *chip, unsigned char spread_spectrum_config)
{
    return i2c_write_reg(chip->i2c_dev, chip->spread_spectrum_reg, spread_spectrum_config);
}

static int _is3729_chip_reset(struct is3729_chip *chip)
{
    return i2c_write_reg(chip->i2c_dev, chip->reset_reg, IS3729_RESET_VAL);
}

int _is3729_write_reg(void *led_chip, unsigned char reg_addr, unsigned char reg_value)
{
    struct is3729_chip *chip;

    if (led_chip == NULL) {
        LOGE(TAG, "%s: led_chip is NULL!", __func__);
        return -1;
    }
    chip = (struct is3729_chip *)led_chip;

    //printf("%s: write reg:%02x data:%02x\n", __func__, reg_addr, reg_value);
    return i2c_write_reg(chip->i2c_dev, reg_addr, reg_value);
}

static void _is3729_esd_guard(struct is3729_chip *chip)
{
    bool init_result;
    static int count = 0;

    if (chip->is_esd_guard_enable == false) {
        return;
    }

    count++;
    if (count >= 10) {
        count = 0;
    } else {
        return;
    }

    init_result = _is3729_chip_init_check(chip);
    if (init_result == false) {
        _is3729_chip_init(chip, LED_INIT_ALL);
    }

    return;
}

static void _is3729_chip_init(struct is3729_chip *chip, int init_type)
{
	if (init_type == LED_INIT_ALL) {
        gpio_write(chip->sdb_gpio, 0);
        aos_msleep(2);
    }

    aos_msleep(2);

    if (init_type == LED_INIT_NONE) {
        return;
    }

    /* do not reset cause it will make led flicker */
    if (init_type != LED_INIT_NORESET) {
        _is3729_chip_reset(chip);
        aos_msleep(2);
    }

    /* reset all pixels pwm to prevent from flicker */
    // is3729_switch_light(chip, SWITCH_TYPE_ALL_OFF);

    _is3729_set_global_current(chip, IS3729_GCC_MAX_VAL);
    _is3729_set_pwm_frequency(chip, IS3729_PWM_29K_VAL);
    _is3729_set_pull_downup(chip, IS3729_PULL_DOWNUP_DEF_VAL);
    _is3729_set_spread_spectrum(chip, IS3729_SPREAD_SPECTRUM_DEF_VAL);
    // _is3729_set_brightness(chip, chip->brightness);
    _is3729_set_config(chip, chip->config);

    uint8_t buf[IS3729_MAX_LED_LEN] = {0};
    _is3729_show(chip, buf);
    gpio_write(chip->sdb_gpio, 1);
    return;
}

static bool _is3729_chip_init_check(struct is3729_chip *chip)
{
    bool is_init_done = false;
    unsigned char config_val = 0;

    /* get chip current config */
    i2c_read_reg(chip->i2c_dev, chip->config_reg, &config_val);
    if (config_val == chip->config) {
        is_init_done = true;
    } else {
        LOGE(TAG, "%s: is_init_done: expect=%d read=%d", __func__, chip->config, config_val);
    }

    // printf("%s: is_init_done:%d \n", __func__, is_init_done);

    return is_init_done;
}

int _is3729_chip_boot_init(void *chip_led)
{
    int i;
    bool init_result = false;

    struct is3729_chip *chip = (struct is3729_chip *)chip_led;
    CHECK_RET_WITH_RET(chip, -1);

    for (i = 0; i < IS3729_INIT_TRY_CNT; i++) {
        _is3729_chip_init(chip, LED_INIT_ALL);
        init_result = _is3729_chip_init_check(chip);
        if (init_result == true) {
            break;
        } else {
            LOGE(TAG, "%s: cnt:%d _is3729_chip_init fail!", __func__, i);
            //mdelay(50);
            aos_msleep(50);
            continue;
        }
    }

    if (i == IS3729_INIT_TRY_CNT) {
        LOGE(TAG, "%s: _is3729_chip_init fail cnt exceed %d!", __func__, IS3729_INIT_TRY_CNT);
    }

    chip->initialized = init_result ? 1 : 0;

    return init_result ? 0 : -1;
}

void _is3729_disable(void *chip_led)
{
    struct is3729_chip *chip = (struct is3729_chip *)chip_led;
    CHECK_RET_WITH_RET(chip && chip->initialized, );

    gpio_write(chip->sdb_gpio, IS3729_SDB_GPIO_DISABLE);

    return;
}

void _is3729_enable(void *chip_led)
{
    struct is3729_chip *chip = (struct is3729_chip *)chip_led;
    CHECK_RET_WITH_RET(chip && chip->initialized, );

    gpio_write(chip->sdb_gpio, IS3729_SDB_GPIO_ENABLE);

    return;
}

void _is3729_set_brightness(void *chip_led, unsigned char brightness)
{
    int cs_index;

    struct is3729_chip *chip = (struct is3729_chip *)chip_led;
    CHECK_RET_WITH_RET(chip && chip->initialized, );

    for (cs_index = 0; cs_index < IS3729_CS_NUM; cs_index++) {
        _is3729_set_cs_scaling(chip, cs_index, brightness);
    }

    return;
}

void _is3729_show(void *chip_led, unsigned char *led_buf)
{
    struct is3729_chip *chip = (struct is3729_chip *)chip_led;

    if (chip->is_fb_bypass == true) {
        return;
    }

    _is3729_esd_guard(chip);

    uint8_t reg_addrs[IS3729_MAX_LED_LEN];

    /* to accelarate, write the sequential regs in one shot */
   for (int start = 0; start < IS3729_MAX_LED_LEN;) {
        int end = start;
        while (end < IS3729_MAX_LED_LEN && chip->reg_map[end] != 255) {
            /* remap the index to value */
            reg_addrs[end] = led_buf[chip->reg_map[end]];
            end++;
        }

        if (chip->reg_map[start] != 255) {
            i2c_write_regs(chip->i2c_dev, start + 1, &reg_addrs[start], end - start);
        }
        start = end + 1;
   }

    return;
}

int _is3729_led_set(void *chip_led, unsigned char idx, unsigned char val)
{

    struct is3729_chip *chip = (struct is3729_chip *)chip_led;
    CHECK_PARAM(chip && chip->initialized && idx >= 0 && idx < chip->led_num, -1);

    return i2c_write_reg(chip->i2c_dev, chip->led_map[idx], val);
}

// void *_is3729_config(void *dev, int sdb_pin)
void *_is3729_config(void *dev, int sdb_pin, int nrow, int ncol, int brt, uint8_t *reg_map, uint8_t *led_map, int led_num)
{
    // int ret;
    struct is3729_chip *led_chip;

    CHECK_PARAM(dev && sdb_pin >= 0 && nrow > 0 && ncol > 0 && nrow * ncol < IS3729_MAX_LED_LEN \
                && led_num > 0 && led_num < IS3729_MAX_LED_LEN, NULL);

    led_chip = aos_zalloc_check(sizeof(struct is3729_chip));

    led_chip->nrow = nrow;
    led_chip->ncol = ncol;
    led_chip->size = nrow * ncol;

    /* unused index set to invalid value: 255 */
    memset(led_chip->reg_map, 255, IS3729_MAX_LED_LEN);

    /* map the led postion to the reg index */
    for (int i = 0; i < led_chip->size; i++) {
        led_chip->reg_map[reg_map[i] - 1] = i;
    }

    memcpy(led_chip->led_map, led_map, led_num);
    led_chip->led_num = led_num;

    led_chip->i2c_dev_addr = IS3729_I2C_ADDR;
    led_chip->i2c_dev = (i2c_dev_t *)dev;
    led_chip->sdb_gpio = sdb_pin;

    led_chip->config_reg = IS3729_CONFIG_REG;
    led_chip->global_current_reg = IS3729_CLOBAL_CURRENT_REG;
    led_chip->pwm_start_reg = IS3729_PWM_START_REG;
    led_chip->cs_scaling_start_reg = IS3729_CS_SCALING_START_REG;
    led_chip->pwm_freq_reg = IS3729_PWM_FREQUENCY_REG;
    led_chip->reset_reg = IS3729_RESET_REG;
    led_chip->pull_downup_reg = IS3729_PULL_DOWNUP_REG;
    led_chip->spread_spectrum_reg = IS3729_SPREAD_SPECTRUM_REG;
    led_chip->open_short_start_reg = IS3729_OPEN_SHORT_START_REG;

    led_chip->is_fb_bypass = false;
    led_chip->is_gamma_enable = false;
    led_chip->is_esd_guard_enable = true;
    led_chip->is_lowest_sec = false;
    led_chip->config = IS3729_CONFIG_DEF_VAL;
    led_chip->brightness = brt;

    LOGI(TAG, "%s: success", __func__);

    return led_chip;
}




static aos_dev_t *is3729_init(driver_t *drv, void *config, int id)
{
    mdisplay_dev_t *screen = (mdisplay_dev_t *)device_new(drv, sizeof(mdisplay_dev_t), id);

    return (aos_dev_t *)screen;
}

#define is3729_uninit device_free

static int is3729_open(aos_dev_t *dev)
{
    return 0;
}

static int is3729_close(aos_dev_t *dev)
{
    return 0;
}

static int is3729_enable(aos_dev_t *dev)
{
    _is3729_enable(led(dev)->screen_handle);
    return 0;
}

static int is3729_disable(aos_dev_t *dev)
{
    _is3729_disable(led(dev)->screen_handle);
    return 0;
}

static int is3729_set_brightness(aos_dev_t *dev, int brightness)
{
    // int ret;

    if (brightness > 100 || brightness < 0) {
        LOGE(TAG, "brightness should be between 0~100");
        return -1;
    }

    uint8_t br_val = brightness * 255 / 100;
    _is3729_set_brightness(led(dev)->screen_handle, br_val);


    return 0;
}

static int is3729_config(aos_dev_t *dev, mdisplay_config_t *config)
{
    int ret;
    void *screen_hdl = _is3729_config(config->dev_comm, config->enable_pin, config->nrow, \
                    config->ncol, config->brightness, config->matrix_map, config->extled_map, config->extled_num);
    CHECK_RET_WITH_RET(screen_hdl, -1);

    ret = _is3729_chip_boot_init(screen_hdl);
    led(dev)->screen_handle = screen_hdl;

    is3729_set_brightness(dev, config->brightness);

    return ret;
}

static int is3729_show(aos_dev_t *dev, uint8_t *buf, size_t len)
{
    struct is3729_chip *chip = (struct is3729_chip *)(led(dev)->screen_handle);
    CHECK_RET_WITH_RET(chip && chip->initialized, -1);

    if (len > IS3729_MAX_LED_LEN) {
        LOGE(TAG, "buf len err len=%u", len);
        return -1;
    }

    _is3729_show(led(dev)->screen_handle, buf);
    return 0;
}

static int is3729_extled_set(aos_dev_t *dev, uint8_t idx, uint8_t val)
{
    CHECK_PARAM(dev, -1);

    return _is3729_led_set(led(dev)->screen_handle, idx, val);
}

static mdisplay_driver_t mdisplay_driver = {
    .drv = {
        .name   = "is3729",
        .type   = "mdisp",
        .init   = is3729_init,
        .uninit = is3729_uninit,
        .open   = is3729_open,
        .close  = is3729_close,
    },
    .config     = is3729_config,
    .enable     = is3729_enable,
    .disable    = is3729_disable,
    .set_brightness = is3729_set_brightness,
    .show       = is3729_show,
    .extled_set  = is3729_extled_set
};

void mdisplay_is3729_register(int idx)
{
    driver_register(&mdisplay_driver.drv, NULL, idx);
}
